package app.server

import app.comet.Subscriber
import app.server.services.ServiceFactory.engineActor
import im.mange.shoreditch.hipster.Script
import net.liftweb.actor.LiftActor
import scala.collection.concurrent
import im.mange.shoreditch.engine.systems.System
import app.server.services.ServiceFactory
import app.listener.ServerActorListener
import im.mange.shoreditch.engine.listener.CompositeListener
import app.listener.CometListener
import im.mange.shoreditch.engine.registry.TestRunsRegistry
import im.mange.shoreditch.engine.registry.TestsRegistry
import im.mange.shoreditch.engine.listener.LoggingListener
import im.mange.shoreditch.engine.listener.TestRunReportListener
import im.mange.shoreditch.engine.services.Services
import im.mange.shoreditch.engine.registry.SystemsRegistry
import scala.reflect.io.File
import im.mange.shoreditch.engine.model.Test

class ServerActor extends LiftActor {
  //TODO: maybe these should all be Option's instead of defaults ..
  private var systems = Seq[System]()
  private var services = Services(systems)
  private var tests = Seq[Test]()
  private val testRunsRegistry = TestRunsRegistry("registry/testruns")

  //TODO: maybe this should be on: ServerActorListener (and have just one instance of it)
  private val activeTestRuns = concurrent.TrieMap[String, Script]()
  //private var subscribers = Set[Subscriber]()

  this ! Init

  protected def messageHandler: PartialFunction[Any, Unit] = {
    case Init => onInit()
//    case Subscribe(s) => onSubscribe(s)
//    case Unsubscribe(s) => onUnsubscribe(s)
    case SystemsRequest(s) => onSystemsRequest(s)
    case ServicesRequest(s) => onServicesRequest(s)
    case TestsRequest(s) => onTestsRequest(s)
    case TestView(id, s) => onTestView(id, s)
    case ScheduleTestRun(id, s) => onScheduleTestRun(id, s)
    case StopTestRun(id, s) => onStopTestRun(id, s)
    //TODO: should have a case _ => ????
  }

//  private def onExecuteProbeRun() {
//    val thisInstance = this
//    //TODO: old messages should die
//    thisInstance ! createCurrentRunStatusUpdate(/*"Waiting for next run..."*/)
//    thisInstance ! createAllRunsStatusUpdate
//
//    //TODO: should probably be scheduled
//    Thread.sleep(1000) //TODO: make me a config - sleep between probes
//
//    new Thread(new Runnable() {
//      override def run() {
//        //TODO: make me a config - sleep between probe runs
//        currentRun = createProbeRun
//        probeRunHistory.add(currentRun)
//
//        var in = 10
//        while (in > 0) {
//          Thread.sleep(1000)
//          thisInstance ! createMessageUpdate("waiting", "Next run starting in ... " + in)
//          in = in - 1
//        }
//
//        thisInstance ! PreExecuteProbe(currentRun.nextToRun)
//      }
//    }).start()
//  }

  //TODO: get out sooner when !probe.isActive
  //TODO: why does probe took too long kill later probes
  //http://stackoverflow.com/questions/13097754/asynchronous-io-in-scala-with-futures
  //TODO: the message seems out of whack ... it's the not the one we actually checking ...
//  private def doRunRun(probe: Probe): ProbeStatus = {
//    try {
//      //OR blocking
//      val f = future { blocking {
//        //TODO: should probably be scheduled
//        Thread.sleep(1000) //TODO: make me a config - sleep between probes
//        if (probe.isActive) unsafeRun(probe) else ProbeInactive }
//      }
//      f onSuccess { case status => status }
//      f onFailure { case e => probeFailed(exceptionMessage(e), probe) }
//      //TODO: make timeout be configurable
//      Await.result(f, Duration(30, SECONDS))
//    } catch {
//      case e: TimeoutException => {
//        println("### e:" + e + " with " + probe.description)
//        probeFailed("Probe took too long", probe)
//      }
//      //TODO: more gracefully handle ...
//      //net.liftweb.json.JsonParser.ParseException
//      //CancellationException
//      //InterruptedException
//      case e: FileNotFoundException => probeFailed("Probe does not exist", probe)
//      case e: ConnectException => probeFailed("Server not responding", probe)
//      case e: Exception => probeFailed(exceptionMessage(e), probe)
//    }
//  }

//  private def unsafeRun(probe: Probe) = {
//    val probeResponse = Json.deserialise(HttpClient.unsafeGet(probe.url, probe.needsProxy))
//    if (probeResponse.failures.isEmpty) ProbeSuccess else ProbeFailure(probeResponse.failures)
//  }

//  private def onSubscribe(subscriber: Subscriber) {
//    println("### " + DateFormatForHumans.timeNow + " - onSubscribe: " + subscriber)
//    if (!subscribers.contains(subscriber)) {
//      subscribers = subscribers + subscriber
//      println("### " + DateFormatForHumans.timeNow + " - new subscriber, now have: " + subscribers.size)
//    } else {
//      println("### " + DateFormatForHumans.timeNow + " - existing subscriber, still have: " + subscribers.size)
//    }
//
//    subscriber ! Init(currentRun.probes)
//    //TODO: we maybe should push through a current rune status too ...
//    currentRun.executedResults.map { p => subscriber ! ProbeStatusUpdate(p._1, p._2) }
//  }
//
//  private def onUnsubscribe(subscriber: Subscriber) {
//    println("### " + DateFormatForHumans.timeNow + " - onUnsubscribe: " + subscriber)
//    subscribers = subscribers - subscriber
//    println("### " + DateFormatForHumans.timeNow + " - subscriber removed, now have: " + subscribers.size)
//  }

  private def onInit() {
    //TODO: parsing services should always be quick, because it blocks the UI, should be a Future ...
    //TODO: all need to be made safe obviously
    systems = SystemsRegistry("registry/systems").load
    tests = TestsRegistry("registry/tests").load
    services = Services(systems)
 }

  private def onSystemsRequest(subscriber: Subscriber) {
    //TODO: obviously we need to refresh on systems change
    //TODO: return a Systems and let that store the state, get versions, deal with refresh etc
    subscriber ! SystemsResponse(systems.toList)
  }

  private def onServicesRequest(subscriber: Subscriber) {
    //TODO: obviously we need to refresh on system change
    //TODO: obviously we need to refresh regularly anyway (esp when version changes)
    //TODO: return a Services and let what store the state, get versions, deal with refresh etc
    subscriber ! ServicesResponse(services.raw.toList)
  }

  private def onTestsRequest(subscriber: Subscriber) {
    //TODO: obviously we need to refresh on tests change
    //TODO: return a Tests and let what store the state, history etc, deal with refresh etc
    subscriber ! TestsResponse(tests.toList)
  }

  private def onTestView(testId: String, subscriber: Subscriber) {
    val test = loadTest(testId)
    subscriber ! TestContent(testId, test.name, parse(subscriber, test).steps)
  }

  private def onScheduleTestRun(testId: String, subscriber: Subscriber) {
    //TODO: maybe don't do this everyime, or maybe only bits of it ...
    onInit()
    val script = parse(subscriber, loadTest(testId))
    script.beforeRun(testId)
    engineActor() ! script
    activeTestRuns.update(script.testRunId.get, script)
  }

  private def onStopTestRun(testRunId: String, subscriber: Subscriber) {
    val toStop: Option[Script] = removeActive(testRunId)
    println("### Attempting to stop testRunId: " + testRunId + ", found: " + toStop)
    toStop.map(_.abort("Stopped by user"))
  }

  def removeActive(testRunId: String) = activeTestRuns.remove(testRunId)
  private def loadTest(testId: String) = tests.find(_.id == testId).get //ick, but we always have it for now ...

  //TODO: should validate services before running
  //TODO: should validate tests before running
  //TODO: should probably pass the Test() at this point and the Services()
  //TODO: need to add the testId and testRunId etc
  //TODO: this looks a bit weird, we should be creating a testrunid here, since we arent running it yet ...
  private def parse(subscriber: Subscriber, test: Test) = {
    val clock = ServiceFactory.systemClock()
    Script.parse(
      CompositeListener(List(
        ServerActorListener(this),
        LoggingListener()(clock),
        TestRunReportListener(test, testRunsRegistry.directory),
        CometListener(subscriber, test)
      )), services, test.content, test.name)(clock)
  }
}
